package view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import model.Turtle;
import controller.TurtleController;
import buttons.*;
import util.commands.*;
import util.coordTranslate.AbstractCoordinateSystem;
import util.reflection.Reflection;
import util.resources.ResourceManager;
import view.panels.BottomPanel;
import view.panels.TabbedPane;

/**
 * A simple frame that sets the organization of the view.
 * 
 * @author Robert C Duvall
 * @author Anne Weng, Nathan Klug, Alex Lee, Josue Mirtil
 */
public class Frame extends JFrame
{
    private static ResourceManager defaults = ResourceManager.getInstance();

    private static final String TITLE = defaults.getString("Frame_title");

    private static Dimension getDefaultPanelSize(String keyString)
    {
        return defaults.getDimension(keyString);
    }

    private static final Color CANVAS_BORDER_COLOR = defaults
            .getColor("Frame_canvas_border_color");
    // used for serialization
    private static final long serialVersionUID = 1L;
    // What are these?
    private Stack<BufferedImage> myPreviousImage;
    private Stack<AbstractCoordinateSystem> myPreviousCoordinates;
    private Stack<BufferedImage> myFutureImage;
    private Stack<AbstractCoordinateSystem> myFutureCoordinates;
    private TurtleController myController;
    private Canvas myCanvas;

    /**
     * Create frame with the given title and size for the interior canvas.
     * 
     * @param controller
     */
    public Frame(TurtleController controller)
    {
        /*
         * Get some basic stuff done.
         */
        setTitle(TITLE);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        myController = controller;

        /*
         * Make the menu bar.
         */
        setJMenuBar(createMenuBar());

        /*
         * Instantiate some other GUI items and associate them with the
         * controller.
         */
        TabbedPane tabs = new TabbedPane(myController,
                getDefaultPanelSize("Tabbed_panel_size"));
        // TextInput textInput = new TextInput(myController);
        // myController.setTextInput(textInput);
        // PlayButton play = new PlayButton("Play", myController, textInput);

        BottomPanel bottomPanel = new BottomPanel(myController);

        myCanvas = new Canvas(myController, this,
                getDefaultPanelSize("Frame_canvas_size"));
        myCanvas.setBorder(BorderFactory.createLineBorder(CANVAS_BORDER_COLOR));
        myPreviousImage = new Stack<BufferedImage>();
        myPreviousCoordinates = new Stack<AbstractCoordinateSystem>();
        myFutureImage = new Stack<BufferedImage>();
        myFutureCoordinates = new Stack<AbstractCoordinateSystem>();
        addData(myCanvas.getCurrentBackground(), myCanvas.getCoordinates());
        /*
         * Make these items appear.
         */
        // JPanel panel = new JPanel();
        // panel.add(play.getInstance());
        // panel.add(textInput.getInstance());
        Container content = this.getContentPane();
        content.add(tabs, BorderLayout.EAST);
        // content.add(list.getInstance(), BorderLayout.EAST);
        content.add(myCanvas, BorderLayout.CENTER);
        content.add(bottomPanel, BorderLayout.SOUTH);
        this.setContentPane(content);
        /*
         * Done.
         */
        pack();
    }

    /**
     * Create and populate the menu bar. TODO: Should the strings in this be
     * extracted, or is that overkill? TODO: This method is a bit messy. Can we
     * refactor it?
     * 
     * @return MenuBar
     */
    public MenuBar createMenuBar()
    {
        ResourceManager resources = ResourceManager.getInstance();
        resources.addResourcesFromFile("menu");

        Map<String, List<JMenuItem>> radioMap = new HashMap<String, List<JMenuItem>>();

        MenuBar menuBar = new MenuBar();
        // Set up menus.
        String[] menus = resources.getStringArray("menus");
        for (String menu : menus)
        {
            menuBar.establishMenu(menu);
            // Fill menus with their menu items.
            String[] menuItems = resources.getStringArray(menu);
            for (String menuItem : menuItems)
            {
                // Gets parameters from the properties file to create the menu
                // item.
                String[] itemProperties = resources.getStringArray(menuItem);

                String label = itemProperties[0];
                boolean addSeparator = itemProperties[1].equals("true");
                String className = itemProperties[2];
                String mnemonic = itemProperties[3];
                String radioGroup = itemProperties[4];
                @SuppressWarnings("unchecked")
                // Use reflection to add the file item.
                Command<Frame> commandInstance = (Command<Frame>) Reflection
                        .createInstance(className);

                JMenuItem item;
                // Put items into the radio map if they specify a group
                // identifier number.
                if (!radioGroup.equals("none"))
                {
                    item = menuBar.addRadioFileItems(label, menu, addSeparator,
                            this, commandInstance);
                    if (radioMap.get(radioGroup) == null)
                    {
                        radioMap.put(radioGroup, new ArrayList<JMenuItem>());
                    }
                    radioMap.get(radioGroup).add(item);
                }
                else
                {
                    item = menuBar.addFileItems(label, menu, addSeparator,
                            this, commandInstance);
                }

                // Add hotkey if it exists.
                if (mnemonic.length() > 0)
                {
                    item.setAccelerator(KeyStroke.getKeyStroke(mnemonic));
                }
            }
        }
        // Now put similar radio items into ButtonGroups.
        for (List<JMenuItem> menuItems : radioMap.values())
        {
            ButtonGroup group = new ButtonGroup();
            for (JMenuItem menuItem : menuItems)
            {
                group.add(menuItem);
            }
        }
        return menuBar;
    }

    /**
     * Gets the canvas that belongs to this frame.
     * 
     * @return the canvas
     */
    public Canvas getCanvas()
    {
        return myCanvas;
    }

    public void addData(BufferedImage image, AbstractCoordinateSystem coordinates)
    {
        myPreviousImage.push(image);
        myPreviousCoordinates.push(coordinates);
    }

    public void undoCanvas()
    {
        BufferedImage prevImage = myPreviousImage.pop();
        AbstractCoordinateSystem prevCoord = myPreviousCoordinates.pop();
        myFutureImage.push(myCanvas.getCurrentBackground());
        myFutureCoordinates.push(myCanvas.getCoordinates());
        replaceCanvas(prevImage, prevCoord);
    }

    public void redoCanvas()
    {
        BufferedImage futureImage = myFutureImage.pop();
        AbstractCoordinateSystem futureCoord = myFutureCoordinates.pop();
        myPreviousImage.push(myCanvas.getCurrentBackground());
        myPreviousCoordinates.push(myCanvas.getCoordinates());
        replaceCanvas(futureImage, futureCoord);
    }

    public void replaceCanvas(BufferedImage image, AbstractCoordinateSystem coordinates)
    {
        myCanvas.setScreen(image, coordinates);
    }

    public TurtleController getController()
    {
        return myController;
    }

    /**
     * Method called to make changes in the view upon the movement of a turtle
     * 
     * @param turtle
     * @param prevX
     * @param prevY
     * @param nextX
     * @param nextY
     */
    public void turtleMoved(Turtle turtle, double prevX, double prevY, double nextX, double nextY)
    {
        if (myCanvas.isAnimated())
        {
            myCanvas.setLocations(prevX, prevY, nextX, nextY);
            myCanvas.setTurtle(turtle);
            myCanvas.doAnimation();
        }
        else
        {
            myCanvas.paintLine(turtle, prevX, prevY, nextX, nextY);
            myCanvas.repaint();
        }
    }

    public void newCanvas()
    {
        this.remove(myCanvas);
        myCanvas = new Canvas(myController, this,
                getDefaultPanelSize("Frame_canvas_size"));
        Container content = this.getContentPane();
        content.add(myCanvas, BorderLayout.CENTER);
        this.setContentPane(content);
        this.pack();
        this.setVisible(true);
    }

    /**
     * Method called when the clear screen method is implemented
     */
    public void clearScreen()
    {
        myCanvas.clearScreen();
    }

    public void zoomCamera(double zoomFactor)
    {
        myCanvas.zoomCamera(zoomFactor);
    }

    public void moveCamera(int left, int up)
    {
        myCanvas.moveCamera(left, up);

    }
}
